﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AbstraX.Bindings;
using System.ServiceModel;
using System.Xml;
using System.Diagnostics;
using System.CodeDom.Compiler;
using System.IO;
using AbstraX.ServerInterfaces;
using ImpromptuInterface;
using ImpromptuInterface.Dynamic;
using AbstraX.Contracts;
using AbstraX.Bindings.Interfaces;
using AbstraX.TypeMappings;

namespace CodeGenerationPipeline
{
    public class BindingsGenerator : IBindingsGenerator
    {
        private IDomainHostApplication domainHost;

        public BindingsGenerator(IDomainHostApplication domainHost)
        {
            this.domainHost = domainHost;
        }

        public IBindingsTree CreateBindings(string providerTypeName, string id)
        {
            return CreateBindings(Type.GetType(providerTypeName), id);
        }

        public IBindingsTree CreateBindings(Type providerType, string id)
        {
            var binding = new BasicHttpBinding();
            var readerQuotas = new XmlDictionaryReaderQuotas();

            Debug.WriteLine("Connecting to service");

            readerQuotas.MaxStringContentLength = Int32.MaxValue;
            readerQuotas.MaxArrayLength = Int32.MaxValue;
            readerQuotas.MaxBytesPerRead = Int32.MaxValue;
            readerQuotas.MaxDepth = Int32.MaxValue;
            readerQuotas.MaxNameTableCharCount = Int32.MaxValue;

            binding.SendTimeout = TimeSpan.FromMinutes(2);
            binding.OpenTimeout = TimeSpan.FromMinutes(2);
            binding.CloseTimeout = TimeSpan.FromMinutes(1);
            binding.ReceiveTimeout = TimeSpan.FromMinutes(10);
            binding.AllowCookies = false;
            binding.BypassProxyOnLocal = false;
            binding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
            binding.MessageEncoding = WSMessageEncoding.Text;
            binding.TextEncoding = System.Text.Encoding.UTF8;
            binding.UseDefaultWebProxy = true;
            binding.MaxBufferPoolSize = int.MaxValue;
            binding.MaxReceivedMessageSize = int.MaxValue;
            binding.ReaderQuotas = readerQuotas;

            var source = new StringBuilder();
            var endpointAddress = new EndpointAddress(string.Format("http://localhost:5998/Services/{0}.svc/Soap", providerType.FullName.Replace(".", "-")));
            var codeProvider = CodeDomProvider.CreateProvider("CSharp");
            var context = System.Web.HttpContext.Current;
            var request = context.Request;

            var parms = new CompilerParameters
            {
                GenerateExecutable = false,
                GenerateInMemory = true,
                IncludeDebugInformation = false
            };

            var fileInfo = new FileInfo(Path.Combine(request.PhysicalApplicationPath, "bin", @"AbstraXProvider.dll"));

            Debug.Assert(fileInfo.Exists);

            parms.ReferencedAssemblies.AddRange(new string[] 
            { 
                "System.dll", 
                @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.ServiceModel.dll",
                fileInfo.FullName
            });

            source.AppendLine("using System;");
            source.AppendLine("using System.ServiceModel;");
            source.AppendLine("using AbstraX.ServerInterfaces;");
            source.AppendLine("");
            source.AppendLine("namespace " + providerType.Namespace);
            source.AppendLine("{");
            source.AppendLine("\t[ServiceContract]");
            source.AppendLine("\tpublic interface " + providerType.Name);
            source.AppendLine("\t{");
            source.AppendLine(string.Format("\t\t[OperationContract(Action=\"http://tempuri.org/{0}Soap/GetRootID\", ReplyAction=\"http://tempuri.org/{0}Soap/GetRootIDResponse\")]", providerType.Name));
            source.AppendLine("\t\tstring GetRootID();");
            source.AppendLine("\t}");
            source.AppendLine("}");

            var results = codeProvider.CompileAssemblyFromSource(parms, source.ToString());

            var channelFactoryType = typeof(ChannelFactory<>);

            channelFactoryType = channelFactoryType.MakeGenericType(results.CompiledAssembly.GetTypes().First());

            var channelFactoryConstructor = channelFactoryType.GetConstructors().Single(c =>
            {
                var parms2 = c.GetParameters();

                if (parms2.Length == 2)
                {
                    if (parms2[0].ParameterType.Name == "Binding" && parms2[1].ParameterType.Name == "EndpointAddress")
                    {
                        return true;
                    }
                }

                return false;
            });

            var channelFactory = (ChannelFactory) channelFactoryConstructor.Invoke(new object[] { binding, endpointAddress });
            var client = channelFactory.GetType().GetMethods().Single(m => m.Name == "CreateChannel" && m.GetParameters().Length == 0).Invoke(channelFactory, new object[0]);

            var rootId = (string) client.GetType().GetMethod("GetRootID").Invoke(client, new object[0]);

            var host = DNSHelper.Host;
            var providerService = this.domainHost.RegisteredServices[rootId];
            var element = (IElement)providerService.GenerateByID(id);

            var rootElement = new
            {
                ChildElements = new List<IElement>() { element }
            }
            .ActLike<IElement>();

            var bindingsTree = new
            {
                RootBindings = GetBindings(rootElement),
                BindingsTreeName = element.DataType.FullyQualifiedName,
                AbstraXProviderService = providerService
            }
            .ActLike<IBindingsTree>();

            return bindingsTree;
        }

        public void GenerateFrom(IBindingsTree bindingsTree)
        {
            var pipelineService = new PipelineService(this.domainHost);

            pipelineService.OnOutput += (sender, e) =>
            {
                Debug.WriteLine(e.ToString());
            };

            pipelineService.GenerateFrom(new List<IBindingsTree>() { bindingsTree });
        }

        private IEnumerable<IBindingsTreeNode> GetBindings(IElement rootElement)
        {
            foreach (IElement childElement in rootElement.ChildElements)
            {
                var propertyBindings = new List<IPropertyBinding>();
                var dataContext = new List<IDataContextObject>();
                var childNodes = new List<IBindingsTreeNode>();

                var bindingsNode = new
                {
                    Name = childElement.Name,
                    PropertyBindings = propertyBindings,
                    ParentNode = (IBindingsTreeNode) null,
                    ParentSourceElement = (IElement) null,
                    DataContext = dataContext,
                    IsReference = false,
                    ChildNodes = childNodes
                }
                .ActLike<IBindingsTreeNode>();

                var remoteCalls = new List<IOperation>();

                var dataContextObject = new DataContextObject
                {
                    ContextObject = childElement,
                    SupportedOperations = OptionalDataContextOperation.AllButSearch,
                    RemoteCalls = remoteCalls,
                    ConstructType = childElement.DefaultConstructType,
                    ContainerType = childElement.DefaultContainerType
                };

                dataContext.Add(dataContextObject);

                if (childElement.Attributes != null)
                {
                    foreach (IAttribute attribute in childElement.Attributes)
                    {
                        var bindingSource = new
                        {
                            BindingAttribute = attribute,
                            IsSearchable = false
                        }
                        .ActLike<IAbstraXBindingSource>();

                        var propertyBinding = new
                        {
                            Property = (IAttribute) null,
                            PropertyBindingName = attribute.Name,
                            BindingSource = bindingSource,
                            BindingMode = BindingMode.TwoWay
                        }
                        .ActLike<IPropertyBinding>();

                        propertyBindings.Add(propertyBinding);
                    }
                }

                AddBindings(childElement, bindingsNode, childNodes);

                yield return bindingsNode;
            }
        }

        private void AddBindings(IElement element, IBindingsTreeNode bindingParent, List<IBindingsTreeNode> parentChildNodes)
        {
            foreach (IElement childElement in element.ChildElements)
            {
                if (childElement.IsContainer)
                {
                    AddBindings(childElement, bindingParent, parentChildNodes);
                }
                else
                {
                    IBindingsTreeNode nodeReference;

                    if (bindingParent.Tree().Contains(childElement, out nodeReference))
                    {
                        var bindingsNodeReference = new
                        {
                            Name = childElement.Name,
                            PropertyBindings = (List<IPropertyBinding>)null,
                            ParentNode = bindingParent,
                            ParentSourceElement = element,
                            DataContext =  (List<IDataContextObject>)null,
                            ChildNodes = (List<IBindingsTreeNode>)null,
                            IsReference = true,
                            Node = nodeReference
                        }
                        .ActLike<IBindingsTreeNodeReference>();

                        parentChildNodes.Add(bindingsNodeReference);
                    }
                    else
                    {
                        var propertyBindings = new List<IPropertyBinding>();
                        var dataContext = new List<IDataContextObject>();
                        var childNodes = new List<IBindingsTreeNode>();

                        var bindingsNode = new
                        {
                            Name = childElement.Name,
                            PropertyBindings = propertyBindings,
                            ParentNode = bindingParent,
                            ParentSourceElement = element,
                            DataContext = dataContext,
                            IsReference = false,
                            ChildNodes = childNodes
                        }
                        .ActLike<IBindingsTreeNode>();

                        var remoteCalls = new List<IOperation>();

                        var dataContextObject = new DataContextObject
                        {
                            ContextObject = childElement,
                            SupportedOperations = OptionalDataContextOperation.AllButSearch,
                            RemoteCalls = remoteCalls,
                            ConstructType = childElement.DefaultConstructType,
                            ContainerType = childElement.DefaultContainerType
                        };

                        dataContext.Add(dataContextObject);

                        foreach (IAttribute attribute in childElement.Attributes)
                        {
                            var bindingSource = new
                            {
                                BindingAttribute = attribute,
                                IsSearchable = false
                            }
                            .ActLike<IAbstraXBindingSource>();

                            var propertyBinding = new
                            {
                                Property = (IAttribute) null,
                                PropertyBindingName = attribute.Name,
                                BindingSource = bindingSource,
                                BindingMode = BindingMode.TwoWay
                            }
                            .ActLike<IPropertyBinding>();

                            propertyBindings.Add(propertyBinding);
                        }

                        parentChildNodes.Add(bindingsNode);

                        AddBindings(childElement, bindingsNode, childNodes);
                    }
                }
            }
        }
    }
}
